/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.management.demo.demos.web;

import com.example.management.demo.MvpError;
import com.example.management.demo.MvpException;
import com.example.management.demo.UploadRequestDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.util.*;

/**
 * @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
 */
@Controller
@Slf4j
public class BasicService {
    Set<String> SUPPORT_FORMAT = new HashSet<>(Arrays.asList("jpg", "mp4", "png"));
    private static final int CHUNK_SIZE = 1 * 1024 * 1024;
    private static final int MAX_RETRIES = 3;

    public ResponseEntity getResponseEntity(HttpServletRequest request,
                                            String resourceType, String idFile) throws Exception {
        File path = getResourceDir(resourceType);
        File file = new File(path, idFile);
        //获取Range头信息
        String rangeHeader = request.getHeader("Range");
        //创建ResourceRegion对象，表示请求的视频片段
        ResourceRegion resourceRegion = getResourceRegion(file, rangeHeader);
        //创建HttpHeaders象，设置Content-Range、Content-Length、Content-Type等响应头信息
        HttpHeaders headers = new HttpHeaders();
        headers.setContentLength(resourceRegion.getCount());
        headers.setContentType(MediaType.valueOf(Files.probeContentType(file.toPath())));
        //构造ResponseEntity对象，表示响应
        return ResponseEntity.status(rangeHeader == null ? HttpStatus.OK : HttpStatus.PARTIAL_CONTENT)
                .headers(headers)
                .body(resourceRegion);
    }

    private ResourceRegion getResourceRegion(File file, String rangeHeader) {
        if (rangeHeader == null) {//如果没有Range头信息，则返回整个文件内容
            return new ResourceRegion(new FileSystemResource(file), 0, file.length());
        }
        //解析Range头信息，获取起始位置和结束位置
        String[] rangeValues = rangeHeader.replace("bytes=", "").split("-");
        long start = Long.parseLong(rangeValues[0]);
        long end = file.length() - 1;
        if (rangeValues.length > 1) {
            end = Long.parseLong(rangeValues[1]);
        }
        long count = end - start + 1;
        Resource resource = new FileSystemResource(file);
        return new ResourceRegion(resource, start, count);
    }

    private File getResourceDir(String resourceType) {
        return new File("D:\\JavaScript\\MVP-Management\\src\\main\\resources\\static");
    }


    public boolean handleVerifyUpload(UploadRequestDTO request) throws Exception {
        String fileName = request.getFileName();
        String extension = FilenameUtils.getExtension(fileName);
        return new File(getResourceDir(""), request.getMd5() + "." + extension).exists();
    }


    public void handleFilechunk(MultipartFile file, UploadRequestDTO request) throws Exception {
        File dir = getResourceDir(request.getType());
        String extension = FilenameUtils.getExtension(request.getFileName().toLowerCase(Locale.ROOT));

        if (!SUPPORT_FORMAT.contains(extension)) {
            throw new Exception("格式不支持");
        }
        File chunkDir = new File(dir, request.getMd5());
        if (!chunkDir.exists()) {
            chunkDir.mkdirs();
        }
        File out = new File(chunkDir, String.valueOf(request.getFileChunkNum()));
        if (out.exists()) {
            return;
        }
        file.transferTo(out);
    }

    public void handleMerge(UploadRequestDTO request) throws Exception {
        File dir = getResourceDir(request.getType());
        File destFile = new File(dir, request.getMd5() + "." + FilenameUtils.getExtension(request.getFileName()));
        if (destFile.exists()) {//检查目标文件是否已经存在
            return;
        }
        String ext = FilenameUtils.getExtension(request.getFileName()).toLowerCase(Locale.ROOT);
        if (!SUPPORT_FORMAT.contains(ext)) {//检查格式是否在白名单内
            throw new MvpException("格式不支持", MvpError.FILE_FORMAT_NOT_SUPPORTED);
        }
        File chunkDir = new File(dir, request.getMd5());
        File[] files = chunkDir.listFiles();
        if (files == null || files.length == 0) {//检查切片是否存在
            throw new MvpException("切片文件不存在", MvpError.FILE_NOT_FOUND);
        }

        int retryCount = 0;//记录尝试获取锁的次数
        try (
                RandomAccessFile raf = new RandomAccessFile(destFile, "rw");
                FileChannel channel = raf.getChannel()
        ) {
            while (true) {
                FileLock lock = channel.tryLock();
                if (lock != null) {
                    try {
                        for (File file : files) {
                            FileChannel inChannel = new RandomAccessFile(file, "r").getChannel();
                            long position = Long.parseLong(file.getName()) * CHUNK_SIZE;
                            raf.seek(position);

                            channel.transferFrom(inChannel, position, inChannel.size());
                            inChannel.close();
                        }
                        break;
                    } finally {
                        lock.release();
                        FileUtils.deleteDirectory(chunkDir);
                    }
                } else if (retryCount >= MAX_RETRIES) {
                    throw new MvpException("重试次数达到上限", MvpError.RETRY_LIMIT_EXCEEDED);
                } else {
                    retryCount++;
                    Thread.sleep(1000);
                }
            }
        } catch (IOException e) {
            throw new MvpException("文件写入失败", MvpError.FILE_WRITE_ERROR);
        }
    }
}
